﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using HadronData;

namespace Hadron.Entities
{
    class Crate
        : Entity
    {
        private static Polygon s_polygonContact, s_polygonVisible;

        private bool m_bDelivered, m_bTracted;
        private Polygon m_polygonContact, m_polygonVisible;

        public Crate(EntityMarker p_entityMarker, Environment p_environment)
            :base(p_entityMarker, p_environment)
        {
            if (s_polygonContact == null)
            {
                s_polygonContact = new Polygon();
                s_polygonContact.Edges.Add(new Edge(-12.0f, -12.0f, 12.0f, -12.0f));
                s_polygonContact.Edges.Add(new Edge(-12.0f, -12.0f, -12.0f, 12.0f));
                s_polygonContact.Edges.Add(new Edge(12.0f, 12.0f, 12.0f, -12.0f));
                s_polygonContact.Edges.Add(new Edge(12.0f, 12.0f, -12.0f, 12.0f));
                s_polygonContact.UpdateBounds();

                s_polygonVisible = new Polygon();
                s_polygonVisible.Edges.Add(new Edge(-12.0f, -12.0f, 12.0f, -12.0f));
                s_polygonVisible.Edges.Add(new Edge(-12.0f, -12.0f, -12.0f, 12.0f));
                s_polygonVisible.Edges.Add(new Edge(12.0f, 12.0f, 12.0f, -12.0f));
                s_polygonVisible.Edges.Add(new Edge(12.0f, 12.0f, -12.0f, 12.0f));

                s_polygonVisible.Edges.Add(new Edge(-8.0f, -10.0f, 8.0f, -10.0f));
                s_polygonVisible.Edges.Add(new Edge(-8.0f, -10.0f, 0.0f, -2.0f));
                s_polygonVisible.Edges.Add(new Edge(8.0f, -10.0f, 0.0f, -2.0f));

                s_polygonVisible.Edges.Add(new Edge(-8.0f, 10.0f, 8.0f, 10.0f));
                s_polygonVisible.Edges.Add(new Edge(-8.0f, 10.0f, 0.0f, 2.0f));
                s_polygonVisible.Edges.Add(new Edge(8.0f, 10.0f, 0.0f, 2.0f));

                s_polygonVisible.Edges.Add(new Edge(-10.0f, -8.0f, -10.0f, 8.0f));
                s_polygonVisible.Edges.Add(new Edge(-10.0f, -8.0f, -2.0f, 0.0f));
                s_polygonVisible.Edges.Add(new Edge(-10.0f, 8.0f, -2.0f, 0.0f));

                s_polygonVisible.Edges.Add(new Edge(10.0f, -8.0f, 10.0f, 8.0f));
                s_polygonVisible.Edges.Add(new Edge(10.0f, -8.0f, 2.0f, 0.0f));
                s_polygonVisible.Edges.Add(new Edge(10.0f, 8.0f, 2.0f, 0.0f));

                s_polygonVisible.UpdateBounds();
            }

            m_nPoints = 750;

            m_bDelivered = false;
            m_bTracted = false;

            m_polygonContact = new Polygon(s_polygonContact);

            m_polygonVisible = new Polygon(s_polygonVisible);
            m_polygonVisible.Colour = new Color(128, 128, 64);

            m_circleBounds = s_polygonContact.GetBoundingCircle(Vector2.Zero);
        }

        public override void Update(GameTime p_gameTime)
        {
            float fDeltaTime = (float)p_gameTime.ElapsedGameTime.TotalSeconds;

            // contact geometry
            m_polygonContact.Transform(m_trnPlacement, s_polygonContact);

            // visible geometry
            m_polygonVisible.Transform(m_trnPlacement, s_polygonVisible);

            PlayerShip playerShip = m_environment.PlayerShip;
            Vector2 vecOffset = playerShip.Position - Position;

            // check if crate can be tracted
            if (!m_bDelivered && !playerShip.TractorBeam && !playerShip.Shield)
            {
                m_bTracted = vecOffset.Length() < 96.0f;
                if (m_bTracted)
                    playerShip.TractorBeam = true;
            }

            if (m_bTracted)
            {
                Vector2 vecDirection = vecOffset;
                vecDirection.Normalize();
                Vector2 vecRelativeVelocity = m_vecVelocity - playerShip.Velocity;

                float fDelta = vecOffset.Length() - 32.0f;
                m_vecVelocity += ((vecDirection * fDelta) - vecRelativeVelocity) * fDeltaTime;
                playerShip.Velocity += (vecDirection * -fDelta + vecRelativeVelocity) * fDeltaTime;

                // cancel traction if ship destroyed, if shield on or if too far
                if (playerShip.Status != PlayerShipStatus.Active
                    || playerShip.Shield
                    || vecOffset.Length() >= 96.0f)
                {
                    m_bTracted = false;
                    playerShip.TractorBeam = false;
                }
            }

            // gravity
            m_vecVelocity += m_environment.Gravity * fDeltaTime;

            // integrate position
            m_trnPlacement.Translation += m_vecVelocity * fDeltaTime;

            m_circleBounds.Centre = m_trnPlacement.Translation;

            // collision with level
            List<ContactPoint> listContactPoints = new List<ContactPoint>();
            m_environment.CurrentLevel.ComputeContactPoints(m_polygonContact, listContactPoints);
            if (listContactPoints.Count > 0)
            {
                ContactPoint contactPoint = listContactPoints[0];
                if (Vector2.Dot(contactPoint.Normal, m_vecVelocity) < 0.0f)
                {
                    Vector2 vecNewVelocity = m_environment.ReflectVector(contactPoint.Normal, m_vecVelocity) * 0.25f;
                    m_vecVelocity = vecNewVelocity;
                }
                Position += contactPoint.Normal * 0.5f;
                float fDeltaOrientation = Orientation - (float) Math.Atan2(contactPoint.Normal.X, -contactPoint.Normal.Y);
                Orientation -= fDeltaOrientation * fDeltaTime * 8.0f;
            }

            // check if delivered
            if (!m_bDelivered && m_vecVelocity.Length() < 16.0f)
            {
                foreach (Entity entity in m_environment.Entities)
                {
                    if (entity.Type != EntityType.CargoStation)
                        continue;

                    if (!m_polygonContact.Intersects(entity.Geometry))
                        continue;

                    m_bDelivered = true;
                    m_bTracted = false;
                    playerShip.TractorBeam = false;
                    m_polygonVisible.Colour = new Color(32, 32, 32);
                    ++playerShip.CratesDelivered;

                    // score
                    m_environment.Context.Score += m_nPoints;

                    return;
                }
            }

            if (m_bDelivered)
            {
                foreach (Entity entity in m_environment.Entities)
                {
                    if (entity.Type != EntityType.CargoStation)
                        continue;

                    if (!m_polygonContact.Intersects(entity.Geometry))
                        continue;

                    vecOffset = entity.Position - Position;
                    Position += vecOffset * fDeltaTime;

                    return;
                }
            }
        }

        public override void Draw(SpriteBatch p_spriteBatch)
        {
            m_polygonVisible.Draw(p_spriteBatch);

            if (m_bTracted)
            {
                PlayerShip playerShip = m_environment.PlayerShip;

                Color colorBeam = new Color(0, 128, 255);
                for (int nIndex = 0; nIndex < 4; nIndex++)
                {
                    int nVertex = s_random.Next(m_polygonVisible.Edges.Count);
                    Vector2 vecVertex = m_polygonVisible.Edges[nVertex].Start;
                    Edge edge = new Edge(playerShip.Position, vecVertex, colorBeam);
                    edge.Draw(p_spriteBatch);
                }
            }
        }

        public override EntityType Type
        {
            get { return EntityType.Crate; }
        }

        public override bool Hostile
        {
            get { return false; }
        }

        public override bool Destructuble
        {
            get { return false; }
        }

        public override Geometry Geometry
        {
            get { return m_polygonContact; }
        }

        public bool Delivered
        {
            get { return m_bDelivered; }
            set { m_bDelivered = value; }
        }
    }
}
